/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.services.system;

import cn.easyplatform.ServiceException;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.ResourceBean;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.services.AbstractService;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.services.IScheduleService;
import cn.easyplatform.services.SystemServiceId;
import cn.easyplatform.services.job.DefaultJobWorker;
import cn.easyplatform.services.job.DynamicJobWorker;
import cn.easyplatform.services.job.SessionValidationJob;
import cn.easyplatform.spi.engine.EngineFactory;
import cn.easyplatform.type.StateType;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class SchedulerService extends AbstractService implements
        IScheduleService {

    private static Logger log = LoggerFactory.getLogger(SchedulerService.class);

    private Scheduler sched;

    private Date startTime;

    public SchedulerService() {
        try {
            StringBuilder sb = new StringBuilder(EngineFactory.me().getConfig("easyplatform.conf"));
            sb.append(File.separatorChar).append("quartz.properties");
            if (log.isDebugEnabled())
                log.debug(I18N
                        .getLabel("easyplatform.sys.scheduler.config", sb));
            SchedulerFactory factory = new StdSchedulerFactory(sb.toString());
            sched = factory.getScheduler();
        } catch (SchedulerException ex) {
            throw new ServiceException(I18N.getLabel(
                    "easyplatform.sys.common.fail",
                    I18N.getLabel("easyplatform.sys.scheduler.name"),
                    ex.getMessage()));
        }
    }

    @Override
    public String getId() {
        return SystemServiceId.SYS_SCHEDULER;
    }

    @Override
    public String getName() {
        return I18N.getLabel("easyplatform.sys.scheduler.name");
    }

    @Override
    public String getDescription() {
        return I18N.getLabel("easyplatform.sys.scheduler.desp");
    }

    @Override
    public void start() throws ServiceException {
        if (getState() != StateType.STOP)
            return;
        try {
            if (log.isDebugEnabled())
                log.debug(I18N.getLabel("easyplatform.sys.common.starting",
                        getName()));
            //会话管理
            int sessionValidationInterval = engineConfiguration.getPlatformAttributeAsInt("sessionValidationInterval");
            if (sessionValidationInterval == 0)
                sessionValidationInterval = 60;
            JobDetail job = JobBuilder.newJob().ofType(SessionValidationJob.class).withIdentity("SessionValidationJob", "J2PaaS").build();
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("SessionValidationJob", "J2PaaS").startAt(new Date(System.currentTimeMillis() + 60000)).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(sessionValidationInterval)).build();
            sched.scheduleJob(job, trigger);

            sched.start();
            setState(StateType.START);
            startTime = new Date();
            if (log.isDebugEnabled())
                log.debug(I18N.getLabel("easyplatform.sys.common.started",
                        getName()));
        } catch (Exception e) {
            throw new ServiceException(I18N.getLabel(
                    "easyplatform.sys.common.fail", getName(), e.getMessage()));
        }
    }

    @Override
    public void stop() throws ServiceException {
        if (getState() != StateType.STOP) {
            if (log.isDebugEnabled())
                log.debug(I18N.getLabel("easyplatform.sys.common.stopping",
                        getName()));
            try {
                if (sched != null && sched.isStarted()) {
                    try {
                        sched.shutdown(true);
                    } catch (SchedulerException ex) {
                        if (log.isWarnEnabled())
                            log.warn(I18N.getLabel(
                                    "easyplatform.sys.common.stop.fail", getName(),
                                    ex.getMessage()));
                    }
                }
            } catch (Exception e) {
            }
            setState(StateType.STOP);
            startTime = null;
            if (log.isDebugEnabled())
                log.debug(I18N.getLabel("easyplatform.sys.common.stopped",
                        getName()));
        }
    }

    @Override
    public Map<String, Object> getRuntimeInfo() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(I18N.getLabel("easyplatform.sys.common.start.time"), startTime);
        try {
            map.put("ActiveCount", sched.getCurrentlyExecutingJobs().size());
            int count = 0;
            for (String name : sched.getJobGroupNames())
                count += sched.getJobKeys(GroupMatcher.groupEquals(name)).size();
            count += sched.getJobKeys(GroupMatcher.groupEquals(SystemServiceId.SYS_PLATFORM)).size();
            map.put("MaxCount", count);
        } catch (SchedulerException e) {
        }
        return map;
    }

    @Override
    public void start(IProjectService service, ResourceBean job) {
        if (log.isDebugEnabled())
            log.debug("startJob {}", job.getName());
        String jobImpl = job.getProps().get("class");
        try {
            Class<?> clazz = null;
            if (Strings.isBlank(jobImpl))
                clazz = DefaultJobWorker.class;
            else
                clazz = Class.forName(jobImpl);
            JobDataMap jdm = new JobDataMap();
            jdm.put("job", job);
            JobDetail jobDetail = JobBuilder.newJob().ofType((Class<? extends Job>) clazz).withIdentity(job.getId(), service.getId()).withDescription(job.getName()).setJobData(jdm).build();
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getId(), service.getId()).startAt(new Date(System.currentTimeMillis()
                    + (Nums.toInt(job.getProps().get("delay"), 60) * 1000))).withSchedule(CronScheduleBuilder.cronSchedule(job.getProps().get(
                    "expression"))).build();
            sched.scheduleJob(jobDetail, trigger);
        } catch (ClassNotFoundException ex) {
            throw new ServiceException("Class '" + jobImpl + "' not found.");
        } catch (Exception ex) {
            throw new ServiceException("Could not start job " + job.getId(), ex);
        }
    }

    @Override
    public void stop(String pid, String id) {
        try {
            TriggerKey tk = new TriggerKey(id, pid);
            sched.pauseTrigger(tk);
            sched.unscheduleJob(tk);
            sched.deleteJob(new JobKey(id, pid));
        } catch (Exception ex) {
            if (log.isWarnEnabled())
                log.warn("stop job:" + id, ex);
        }
    }

    @Override
    public void stopGroup(String pid) {
        try {
            GroupMatcher<TriggerKey> matcher = GroupMatcher.groupEquals(pid);
            //sched.pauseTriggers(matcher);
            sched.unscheduleJobs(new ArrayList<>(sched.getTriggerKeys(matcher)));
            sched.deleteJobs(new ArrayList<>(sched.getJobKeys(GroupMatcher.groupEquals(pid))));
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not stopGroup " + pid, ex);
        }
    }

    @Override
    public int getState(String pid, String id) {
        try {
            TriggerKey tk = new TriggerKey(id, pid);
            Trigger.TriggerState state = sched.getTriggerState(tk);
            if (state == Trigger.TriggerState.NONE)
                return StateType.STOP;
            else if (state == Trigger.TriggerState.PAUSED)
                return StateType.PAUSE;
            else
                return StateType.START;
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not get state " + id, ex);
        }
    }

    @Override
    public void pauseGroup(String pid) {
        try {
            GroupMatcher<TriggerKey> matcher = GroupMatcher.groupEquals(pid);
            sched.pauseTriggers(matcher);
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not pauseJobGroup " + pid, ex);
        }
    }

    @Override
    public void pause(String pid, String id) {
        try {
            sched.pauseJob(new JobKey(id, pid));
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not pauseJob " + pid, ex);
        }
    }

    @Override
    public void resumeGroup(String pid) {
        try {
            GroupMatcher<TriggerKey> matcher = GroupMatcher.groupEquals(pid);
            sched.resumeTriggers(matcher);
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not resumeJobGroup " + pid, ex);
        }
    }

    @Override
    public void resume(String pid, String id) {
        try {
            sched.resumeJob(new JobKey(id, pid));
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not resumeJob " + pid, ex);
        }
    }

    @Override
    public boolean modify(String pid, String id, Object time) {
        try {
            Trigger oldTrigger = sched.getTrigger(new TriggerKey(id, pid));
            Trigger newTrigger;
            if (time instanceof Date) {
                newTrigger = TriggerBuilder.newTrigger().withIdentity(id, pid).usingJobData(oldTrigger.getJobDataMap()).startAt((Date) time).build();
            } else {
                newTrigger = TriggerBuilder.newTrigger().withIdentity(id, pid).withSchedule(CronScheduleBuilder.cronSchedule((String) time)).build();
            }
            return sched.rescheduleJob(new TriggerKey(id, pid), newTrigger) != null;
        } catch (Exception ex) {
            throw new ServiceException("Could not modify " + pid, ex);
        }
    }

    @Override
    public Date getStartTime() {
        return startTime;
    }

    @Override
    public Map<String, Object> getRuntimeInfo(String pid, String id) {
        Map<String, Object> runtimeInfo = new HashMap<String, Object>();
        try {
            Trigger trigger = sched.getTrigger(new TriggerKey(id, pid));
            if (trigger != null) {
                Date startTime = trigger.getStartTime();
                if (startTime != null)
                    runtimeInfo.put("service.start.time", startTime);
                if (pid.endsWith(".dynamic")) {//动态任务
                    JobDataMap jdm = trigger.getJobDataMap();
                    runtimeInfo.put("entity", jdm.get("entity"));
                    runtimeInfo.put("name", jdm.get("name"));
                    runtimeInfo.put("user", jdm.get("user"));
                } else {
                    Date prevFireTime = trigger.getPreviousFireTime();
                    if (prevFireTime != null)
                        runtimeInfo.put("service.prev.time", prevFireTime);
                    Date nextFireTime = trigger.getNextFireTime();
                    if (nextFireTime != null)
                        runtimeInfo.put("service.next.time", nextFireTime);

                }
            }
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not getRuntimeInfo " + pid, ex);
        }
        return runtimeInfo;
    }

    @Override
    public Collection<String> getJobKeys(String pid) {
        try {
            Set<JobKey> set = sched.getJobKeys(GroupMatcher.groupEquals(pid));
            List<String> jobKeys = new ArrayList<>(set.size());
            set.forEach(jobKey -> {
                jobKeys.add(jobKey.getName());
            });
            return jobKeys;
        } catch (SchedulerException ex) {
            throw new ServiceException("Could not getJobKeys " + pid, ex);
        }
    }

    @Override
    public void scheduleDynamicJob(String pid, long jobId, String entity, String userId, Date time) {
        JobDataMap dm = new JobDataMap();
        dm.put("entity", entity);
        dm.put("user", userId);
        dm.put("name", "");
        dm.put("pid", pid);
        JobDetail jobDetail = JobBuilder.newJob().ofType(DynamicJobWorker.class).withIdentity(String.valueOf(jobId), pid + ".dynamic").build();
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(String.valueOf(jobId), pid + ".dynamic").usingJobData(dm).startAt(time).build();
        try {
            sched.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException ex) {
            throw new ServiceException(String.format("Could not start job %s", jobId),
                    ex);
        }
    }
}
